package com.elitesland.system.service.impl;

import cn.hutool.core.util.StrUtil;
import com.elitesland.core.base.PagingVO;
import com.elitesland.system.convert.SysNextNumberConvert;
import com.elitesland.system.entity.QSysNextNumberDO;
import com.elitesland.system.param.SysNextNumberQParam;
import com.elitesland.system.repo.SysNextNumberRepo;
import com.elitesland.system.service.SysNextNumberService;
import com.elitesland.system.vo.SysNextNumberVO;
import com.querydsl.core.types.ExpressionUtils;
import com.querydsl.core.types.Predicate;
import lombok.val;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

import java.time.DayOfWeek;
import java.time.LocalDateTime;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;

/**
 * <pre></pre>
 *
 * @author mir
 * @date 2020-10-12
 */
@Service
public class SysNextNumberServiceImpl implements SysNextNumberService {

    private SysNextNumberRepo sysNextNumberRepo;

    @Autowired
    public void setSysNextNumberRepo(SysNextNumberRepo sysNextNumberRepo) {
        this.sysNextNumberRepo = sysNextNumberRepo;
    }

    @Override
    @Transactional(rollbackFor = {Exception.class}, propagation = Propagation.REQUIRES_NEW)
    public Long getNextNumber(String code) {
        var newNextNumber = 0L;

        var i = 0;
        while (i < 5) {
            try {
                var nextNumber = 0L;

                val nndOpt = sysNextNumberRepo.getByCode(code);
                if (nndOpt.isEmpty()) {
                    i++;
                    continue;
                }
                val nnd = nndOpt.get();
                if (StrUtil.isNotBlank(nnd.getNnPeriod())) {
                    if (nnd.getNnTime() != null) {
                        val nowY = LocalDateTime.now().getYear();
                        val nnY = nnd.getNnTime().getYear();

                        val nowWD = LocalDateTime.now().getDayOfWeek();
                        val nnWD = nnd.getNnTime().getDayOfWeek();

                        switch (nnd.getNnPeriod()) {
                            case "D":
                                val nowD = LocalDateTime.now().getDayOfYear();
                                val nnD = nnd.getNnTime().getDayOfYear();
                                if (nowD - nnD > 0) {
                                    nextNumber = 1L;
                                } else {
                                    nextNumber = nnd.getNextNumber();
                                }
                                break;
                            case "W":
                                if (nowY != nnY || (nowWD == DayOfWeek.MONDAY && nowWD != nnWD)) {
                                    nextNumber = 1L;
                                } else {
                                    nextNumber = nnd.getNextNumber();
                                }
                                break;
                            case "M":
                                val nowMD = LocalDateTime.now().getMonthValue();
                                val nnMD = LocalDateTime.now().getMonthValue();
                                if (nowMD != nnMD) {
                                    nextNumber = 1L;
                                } else {
                                    nextNumber = nnd.getNextNumber();
                                }
                                break;
                            case "Y":
                                if (nowY != nnY) {
                                    nextNumber = 1L;
                                } else {
                                    nextNumber = nnd.getNextNumber();
                                }
                                break;
                            default:
                                nextNumber = nnd.getNextNumber();
                        }
                    }
                } else {
                    nextNumber = nnd.getNextNumber();
                }

                nnd.setNextNumber(nextNumber + (nnd.getStep() == null || nnd.getStep() <= 0 ? 1 : nnd.getStep()))
                        .setNnTime(LocalDateTime.now());

                sysNextNumberRepo.save(nnd);

                newNextNumber = nextNumber;

                break;
            } catch (Exception ignored) {
            }

            try {
                Thread.sleep(300);
            } catch (InterruptedException ignored) {
            }

            i++;
        }

        return newNextNumber;
    }


    @Override
    public PagingVO<SysNextNumberVO> search(SysNextNumberQParam param) {
        QSysNextNumberDO nnTable = QSysNextNumberDO.sysNextNumberDO;

        Predicate predicate = nnTable.isNull().or(nnTable.isNotNull());

        predicate = StringUtils.isBlank(param.getCode()) ? predicate : ExpressionUtils.and(
                predicate, nnTable.code.like("%" + param.getCode() + "%")
        );
        predicate = StringUtils.isBlank(param.getName()) ? predicate : ExpressionUtils.and(
                predicate, nnTable.name.like("%" + param.getName() + "%")
        );
        predicate = StringUtils.isBlank(param.getNumberClass()) ? predicate : ExpressionUtils.and(
                predicate, nnTable.numberClass.eq(param.getNumberClass())
        );

        val ret = sysNextNumberRepo.findAll(predicate, param.getPageRequest());
        return PagingVO.<SysNextNumberVO>builder()
                .total(ret.getTotalElements())
                .records(ret.getContent().stream().map(SysNextNumberConvert.INSTANCE::doToVO).collect(Collectors.toList()))
                .build();
    }

    @Override
    public Optional<SysNextNumberVO> oneById(Long id) {
        return sysNextNumberRepo.findById(id).map(SysNextNumberConvert.INSTANCE::doToVO);
    }

    @Override
    public Long create(SysNextNumberVO nextNumber) {
        val newNN = sysNextNumberRepo.save(SysNextNumberConvert.INSTANCE.voToDO(nextNumber));
        return newNN.getId();
    }

    @Override
    public void update(SysNextNumberVO nextNumber) {
        sysNextNumberRepo.findById(nextNumber.getId()).ifPresent(nn -> {
            nn.setCode(nextNumber.getCode())
                    .setName(nextNumber.getName())
                    .setNumberClass(nextNumber.getNumberClass())
                    .setStep(nextNumber.getStep())
                    .setNextNumber(nextNumber.getNextNumber());
            sysNextNumberRepo.save(nn);
        });
    }

    @Override
    public void deleteByIds(List<Long> ids) {
        sysNextNumberRepo.deleteAllByIdIn(ids);
    }
}
